// debug.c
#include "Yecc.h"

void printEscapedString(const char* str) {
    for (int i = 0; str[i] != '\0'; i++) {
        switch (str[i]) {
            case '\n': printf("\\n"); break;
            case '\0': printf("\\0"); break;
            case '\\': printf("\\\\"); break;
            case '\'': printf("\\\'"); break;
            case '\"': printf("\\\""); break;
            default: printf("%c", str[i]); break;
        }
    }
    return ;
}

void printEscapedChar(const char ch) {
    switch (ch) {
        case '\n': printf("\\n"); break;
        case '\0': printf("\\0"); break;
        case '\\': printf("\\\\"); break;
        case '\'': printf("\\\'"); break;
        case '\"': printf("\\\""); break;
        default: printf("%c", ch); break;
    }
    return ;
}

void printToken(struct Token* tok) {

    printf("<");

    switch (tok->kind) {
        case TK_HEAD:    
            printf("TK_HEAD"); 
            break;
        case TK_KEYWORD: 
            printf("TK_KEYWORD: ");
            printf("\"%s\"", tok->name);
            break;
        case TK_IDENT:   
            printf("TK_IDENT:   ");
            printf("\"%s\"", tok->name);
            break;
        case TK_PUNCT:   
            printf("TK_PUNCT:   ");
            printf("\"%s\"", getPunctText(tok->pk));
            break;
        case TK_NUM:     
            printf("TK_NUM:     ");
            printf("\"%d\"", tok->num);
            break;
        case TK_CH:      
            printf("TK_CH:      ");
            printf("\"");
            printEscapedChar(tok->ch);
            printf("\"");
            break;
        case TK_STR:     
            printf("TK_STR:     ");
            printf("\"");
            printEscapedString(tok->str);
            printf("\"");
            break;
        case TK_EOF:     
            printf("TK_EOF");
            break;
        default: 
            assert(0);
    }

    printf(">\n");
    return ;
}

void debug_info_dumpTokens(struct Token* head) {
    struct Token* tok = head;
    while(tok != NULL) {
        printToken(tok);
        tok = tok->next;
    }
    return ;
}

void debug_info_tokenkind(const char* src, enum TokenKind kind, int loc, int len) {
    printf("### debug info: new ");
    switch (kind) {
        case TK_HEAD:    printf("TK_HEAD"); break;
        case TK_KEYWORD: printf("TK_KEYWORD: "); break;
        case TK_IDENT:   printf("TK_IDENT: "); break;
        case TK_PUNCT:   printf("TK_PUNCT: "); break;
        case TK_NUM:     printf("TK_NUM: "); break;
        case TK_CH:      printf("TK_CH: "); break;
        case TK_STR:     printf("TK_STR: "); break;
        case TK_EOF:     printf("TK_EOF"); break;
        default: assert(0);
    }
    for(int i = 0; i < len; i++) printf("%c", src[loc + i]);
    printf(" ###\n");
    return ;
}

void debug_info_dumpSrc(const char* src) {
    printf("%s", src);
    return ;
}

int cnt = 0;

void putSpace(FILE *fp) {
    for (int i = 0; i < cnt; i++) {
        fprintf(fp, "    ");
    }
    return ;
}

char* getNodeChar(struct Node* const nd) {
    
    switch (nd->kind) {
        // Arithmetic
        case ND_ADD:        return "+";
        case ND_SUB:        return "-";
        case ND_MUL:        return "*";
        case ND_DIV:        return "/";
        case ND_MOD:        return "%";
        // Bitwise
        case ND_BITAND:     return "&";
        case ND_BITOR:      return "|";
        case ND_BITXOR:     return "^";
        case ND_BITNOT:     return "~";
        // Shift
        case ND_SHL:        return "<<";
        case ND_SHR:        return ">>";
        // Assign
        case ND_ASSIGN:     return "=";
        case ND_ADD_ASG:    return "+=";
        case ND_SUB_ASG:    return "-=";
        case ND_MUL_ASG:    return "*=";
        case ND_DIV_ASG:    return "/=";
        case ND_MOD_ASG:    return "%=";
        case ND_SHL_ASG:    return "<<=";
        case ND_SHR_ASG:    return ">>=";
        case ND_AND_ASG:    return "&=";
        case ND_OR_ASG:     return "|=";
        case ND_XOR_ASG:    return "^=";
        // Logic
        case ND_LOG_AND:    return "&&";
        case ND_LOG_OR:     return "||";
        case ND_LOG_NOT:    return "!";
        // Compare
        case ND_EQ:         return "==";
        case ND_NEQ:        return "!=";
        case ND_LT:         return "<";
        case ND_LE:         return "<=";
        case ND_GT:         return ">";
        case ND_GE:         return ">=";
        // Inc and Dec
        case ND_INC:        return "++";
        case ND_DEC:        return "--";
        // Struct member assess
        case ND_DOT:        return ".";
        case ND_ARROW:      return "->";
        case ND_COMMA:      return ",";
        case ND_COND:       return "?";

        // Pointers
        case ND_DEREF:      return "*";
        case ND_ADDR:       return "&";

        case ND_FUNCALL:    return "call";
        case ND_INDEX:      return "idx";

        // Key Stmt
        case ND_IF:         return "if";
        case ND_WHILE:      return "while";
        case ND_DO:         return "do";
        case ND_FOR:        return "for";
        case ND_SWITCH:     return "switch";
        case ND_CASE:       return "case";
        case ND_DEFAULT:    return "default";
        case ND_ASM:        return "asm";
        case ND_RETURN:     return "return";
        case ND_BREAK:      return "break";
        case ND_CONTINUE:   return "continue";

        case ND_DECL:       return "decl";
        case ND_SIZEOF:     return "sizeof";
        case ND_CAST:       return "cast";
        case ND_INILIST:    return "inilist";

        // Leaf Node
        // case ND_NUM
        // case ND_CH
        case ND_STR:        return nd->str;
        case ND_IDENT:      return nd->name;
        case ND_TRUE:       return "true";
        case ND_FALSE:      return "false";
        case ND_NULL:       return "NULL";

        default: assert(0);
    }
}

bool isLeafNode(struct Node* const nd) {
    return nd->left == NULL &&
           nd->right == NULL &&
           nd->condi == NULL &&
           nd->body == NULL &&
           nd->els == NULL &&
           nd->init == NULL &&
           nd->inc == NULL &&
           nd->next == NULL;
}

char TypeName[1024];

void getTypeName(const struct Type* type) {

    assert(type != NULL);
    TypeName[0] = '\0'; // clear

    const char* base;
    char buf[1024];
    buf[0] = '\0';

    switch (type->kind) {
        case TY_VOID:  base = "void";  break;
        case TY_BOOL:  base = "bool";  break;
        case TY_CHAR:  base = "char";  break;
        case TY_SHORT: base = "short"; break;
        case TY_INT:   base = "int";   break;
        case TY_LONG:  base = "long";  break;

        case TY_STRUCT:
        case TY_ENUM: {
            buf[0] = '\0';
            char* ch = (type->kind == TY_STRUCT) ? "struct " : "enum ";
            Cpy(buf, ch);
            assert(type->name != NULL);
            Cat(buf, type->name);    // cat "struct " or "enum " + name
            Cpy(TypeName, buf);      // overwrite TypeName
            return ;
        }

        case TY_PTR: {
            getTypeName(type->base);// get base name to TypeName
            buf[0] = '\0';          // clear buf
            Cpy(buf, "ptr->");      // write "ptr->"
            Cat(buf, TypeName);     // cat "ptr->" + base
            Cpy(TypeName, buf);     // overwrite TypeName
            return ;
        }
        case TY_ARY: {
            getTypeName(type->base);
            char numbuf[32];
            Itoa(numbuf, type->len);
            buf[0] = '\0';
            Cpy(buf, "ary, ");
            Cat(buf, numbuf);
            Cat(buf, "->");
            Cat(buf, TypeName);
            Cpy(TypeName, buf);
            return ;
        }

        default: assert(0);
    }

    if(type->sign != SG_UNKNOWN) {
        char* ch = (type->sign == SG_SIGNED) ? "signed " : "unsigned ";
        Cpy(buf, ch);
        Cat(buf, base);
        Cpy(TypeName, buf);
    } else {
        Cpy(TypeName, base);
    }

    return ;
}

void putNode(FILE *fp, struct Node* const root) {
    
    if(root == NULL) return ;

    cnt++;
    fprintf(fp, "\n");
    putSpace(fp);

    switch (root->kind) {
        case ND_NUM:
            fprintf(fp, "{\"name\": \"%ld\",\n", root->num);
            break;
        case ND_CH:
            fprintf(fp, "{\"name\": \"%c\",\n", root->ch);
            break;
        case ND_TYPE:
            getTypeName(root->type);
            fprintf(fp, "{\"name\": \"%s\",\n", TypeName);
            break;
        default:
            fprintf(fp, "{\"name\": \"%s\",\n", getNodeChar(root));
            break;
    }

    putSpace(fp);
    fprintf(fp, "\"children\": [");

    if(isLeafNode(root)) {
        fprintf(fp, "]},\n");
        cnt--;
        return ;
    }

    switch(root->kind) {
        case ND_IF:
            putNode(fp, root->condi);
            putNode(fp, root->body);
            putNode(fp, root->els);
            break;
        case ND_WHILE:
            putNode(fp, root->condi);
            putNode(fp, root->body);
            break;
        case ND_DO:
            putNode(fp, root->body);
            putNode(fp, root->condi);
            break;
        case ND_FOR:
            putNode(fp, root->init);
            putNode(fp, root->condi);
            putNode(fp, root->body);
            putNode(fp, root->inc);
            break;
        case ND_SWITCH:
            putNode(fp, root->condi);
            putNode(fp, root->body);
            break;
        case ND_CASE:
            putNode(fp, root->condi);
            putNode(fp, root->body);
            break;
        case ND_DEFAULT:
            putNode(fp, root->body);
            break;
        case ND_ASM:
            putNode(fp, root->body);
            break;
        case ND_RETURN:
            putNode(fp, root->body);
            break;
        case ND_DECL:
            putNode(fp, root->left);
            putNode(fp, root->right);
            putNode(fp, root->init);
            break;
        case ND_INILIST:
            putNode(fp, root->init);
            break;
        default:
            // op
            putNode(fp, root->condi); // for ND_COND
            putNode(fp, root->left);
            putNode(fp, root->right);
    }

    putNode(fp, root->next);

    putSpace(fp);
    fprintf(fp, "]},\n");
    cnt--;
    return;
}

void VisibleAST(FILE *fp, struct Node* const root) {
    assert(fp != NULL);
    fprintf(fp, "#!/usr/bin/python3.8\n");
    fprintf(fp, "treedata = [");
    putNode(fp, root);
    fprintf(fp, "]\n");
}

void debug_info_dumpAbstractSyntaxTree(struct Node* const root) {
    FILE *fp = fopen("./ASTdata.py", "w");
    assert(fp != NULL);
    VisibleAST(fp, root);
    fclose(fp);
}